#include <GameEngine/Graphics/Meshes/trgl_mesh_io.h>

#include <GameEngine/Graphics/Meshes/trgl_mesh.h>
#include <GameEngine/Graphics/Meshes/trgl_mesh_creator.h>
#include <GameEngine/Core/Files/FileIO.h>
#include <GameEngine/Core/Memory/memory_manager.h>

#include <QtCore/QFile>
#include <QtCore/QTextStream>
#include <QtCore/QStringList>
#include <QtGui/QImage>

#include <niflib/niflib.h>
#include <niflib/obj/NiObject.h>
#pragma warning( push )
#pragma warning( disable : 4996 )
#include <niflib/obj/NiNode.h>
#pragma warning( pop )
#include <niflib/obj/NiTriStrips.h>
#include <niflib/obj/NiTriStripsData.h>
#include <niflib/obj/NiProperty.h>
#include <niflib/obj/NiMaterialProperty.h>
#include <niflib/obj/NiTexturingProperty.h>
#include <niflib/obj/NiSourceTexture.h>
#include <niflib/obj/BSXFlags.h>

#include <lib3ds/lib3ds.h>

#include <iostream>



#ifdef GAMEENGINE_NAMESPACES
namespace GameEngine {
#endif

//=================================================================================

//! Load the mesh from an OBJ file
/*!
 @param mesh The mesh to load
 @param file The path to the OBJ file
 @param scale Rescale the vertex positions of the mesh
 @return True if mesh could be loaded, false otherwise
 */
bool TrglMeshIO::load_obj_file(TrglMesh& mesh, const GEString& file, float scale) {
	// Access the file
	QFile f(file.c_str());
	if ( f.open(QIODevice::ReadOnly | QIODevice::Text) == false ) return false;
	QTextStream in(&f);

	// Get directory path
	GEString dir_path;
	{{
		// Split the file name
		QStringList list = QString(file.c_str()).split("/");
		if ( list.size() > 1 ) {
			QStringList::const_iterator it = list.end(); --it; --it;
			for ( ; it != list.begin(); --it ) {
				dir_path = GEString(it->toAscii().constData()) + "/" + dir_path;
			}
			dir_path = GEString(list.begin()->toAscii().constData()) + "/" + dir_path;
		}
	}}

	// Build a mesh creator
	TrglMeshCreator creator(mesh);
	creator.prepare_mesh_creation();
	QString line;

	// Setup materials
	std::vector<Material> materials;
	std::vector<bool> materials_transparency;
	int mtl_index = 0;

	// Read the file
	{{
		bool state = true, tmp = true, collision = false;
		Point3d<float> pt;
		Vector3d<float> vec;
		TextureCoordinate2d coord;
		int index, nindex, cindex;
		long offset = 0, noffset = 0, coffset = 0;
		bool has_normals = false, has_coords = false;
		while ( in.atEnd() == false ) {
			// Get the current line
			line = in.readLine();
			state = true;

			// Split the line
			QStringList list = line.split(" ");
			list.removeAll( "" );
			int count = list.count();
			if ( count < 1 ) continue;
			QStringList::const_iterator it = list.begin();

			// Material mode
			if ( (*it) == "mtllib" ) {
				if ( count < 2 ) continue;

				// Read the file name
				++it;
				state &= load_mtl_file(materials, materials_transparency, it->toAscii().constData(), dir_path);
				continue;
			}

			// Use material mode
			if ( (*it) == "usemtl" ) {
				if ( count < 2 ) continue;
				if ( materials.empty() ) {
					state = false;
					continue;
				}

				// Read the material properties
				++it; GEString str = it->toAscii().constData();
				Material mat;
				bool tmp = false, mat_use_transparency = false;
				int matidx = -1;
				for ( std::vector<Material>::const_iterator mit = materials.begin(); mit != materials.end(); ++mit ) {
					++matidx;
					if ( str != mit->name() ) continue;
					mat = *mit;
					mat_use_transparency = materials_transparency[matidx];
					tmp = true;
					break;
				}

				// No material found
				if ( tmp == false ) {
					state = false;
					continue;
				}

				// Add previous subgroup (if not empty)
				creator.reset_subgroup();
				creator.subgroup_material() = mat;
				creator.subgroup_use_transparency() = mat_use_transparency;
				continue;
			}

			// Group mode
			if ( (*it) == "g" ) {
				// Get new group name
				++it;
				GEString gname = "Default";
				if ( it != list.end() ) gname = it->toAscii().constData();
				creator.set_group_name(gname);
				collision = ( gname == "collision" );
				continue;
			}

			// Vertex mode
			if ( (*it) == "v" ) {
				if ( collision ) {
					++offset;
					continue;
				}
				if ( count < 4 ) continue;

				// Read the vertex
				++it; pt.x() = (*it).toFloat(&tmp) * scale; state &= tmp;
				++it; pt.y() = (*it).toFloat(&tmp) * scale; state &= tmp;
				++it; pt.z() = (*it).toFloat(&tmp) * scale; state &= tmp;
				if ( state != true ) continue;

				// Add the vertex to the mesh
				creator.add_vertex( pt );
				continue;
			}

			// Normal mode
			if ( (*it) == "vn" ) {
				if ( collision ) {
					++noffset;
					continue;
				}
				if ( count < 4 ) continue;

				// Read the vertex
				++it; vec.x() = (*it).toFloat(&tmp); state &= tmp;
				++it; vec.y() = (*it).toFloat(&tmp); state &= tmp;
				++it; vec.z() = (*it).toFloat(&tmp); state &= tmp;
				if ( state != true ) continue;

				// Add the vertex to the mesh
				creator.add_normal( vec );
				has_normals = true;
				continue;
			}

			// Texture coordinate mode
			if ( (*it) == "vt" ) {
				if ( collision ) {
					++coffset;
					continue;
				}
				if ( count < 3 ) continue;

				// Read the vertex
				++it; coord.u() = (*it).toFloat(&tmp); state &= tmp;
				++it; coord.v() = (*it).toFloat(&tmp); state &= tmp;
				if ( state != true ) continue;

				// Add the vertex to the mesh
				creator.add_texture_coordinate( coord );
				has_coords = true;
				continue;
			}

			// Polygon mode
			if ( (*it) == "f" ) {
				if ( collision ) continue;
				if ( count < 4 ) continue;

				// Read the first triangle
				state &= creator.begin_polygon();
				if ( state ) {
					++it;
					for ( ; it != list.end(); ++it ) {
						QStringList list2 = it->split("/");
						QStringList::iterator it2 = list2.begin();
						index = it2->toInt(&tmp) - offset;
						if ( tmp == false ) {
							break;
						}
						if ( has_coords ) {
							if ( it2 != list2.end() ) ++it2;
							if ( it2 == list2.end() ) cindex = 0;
							else {
								cindex = it2->toInt(&tmp) - coffset;
								if ( tmp == false ) cindex = 0;
							}
						}
						else {
							cindex = 0;
							if ( ( it2->isEmpty() ) && ( it2 != list2.end() ) ) ++it2;
						}
						if ( has_normals ) {
							if ( it2 != list2.end() ) ++it2;
							if ( it2 == list2.end() ) nindex = 0;
							else {
								nindex = it2->toInt(&tmp) - noffset;
								if ( tmp == false ) nindex = 0;
							}
						}
						else nindex = 0;
						creator.add_vertex_index(index-1, nindex-1, cindex-1);
					}
					creator.end_polygon();
				}
				continue;
			}
		}

		// Add last group / subgroup (if not empty)
		creator.reset_subgroup();
	}}

	return true;
}

//! Load the materials associated to the mesh from a MTL file
/*!
 @param materials Receives the materials present in the MTL file
 @param materials_transparency Receives the transparency flags associated to the materials present in the MTL file
 @param file The relative path to the MTL file
 @param dir_path The absolute path of the directory where the MTL file is located
 @return True if materials could be loaded, false otherwise
 */
bool TrglMeshIO::load_mtl_file(std::vector<Material>& materials, std::vector<bool>& materials_transparency, const GEString& file, const GEString& dir_path) {
	// Access the file
	GEString fpath = dir_path + file;
	QFile f(fpath.c_str());
	if ( f.open(QIODevice::ReadOnly | QIODevice::Text) == false ) return false;
	QTextStream in(&f);

	// Read the file
	QString line;
	while ( in.atEnd() == false ) {
		// Get the current line
		line = in.readLine();

		// Split the line
		QStringList list = line.split(" ");
		int count = list.count();
		if ( count < 2 ) continue;

		// New material
		QStringList::const_iterator it = list.begin();
		if ( (*it) != "newmtl" ) continue;

		// Read material
		++it;
		return load_mtl(materials, materials_transparency, it->toAscii().constData(), in, dir_path);
	}

	// No material could be read
	return false;
}

//! Recursively load the materials associated to the mesh from a MTL file
/*!
 @param materials Receives the materials present in the MTL file
 @param materials_transparency Receives the transparency flags associated to the materials present in the MTL file
 @param material_name The name of the current material
 @param in The input text stream
 @param dir_path The absolute path of the directory where the MTL file is located
 @return True if materials could be loaded, false otherwise
 */
bool TrglMeshIO::load_mtl(std::vector<Material>& materials, std::vector<bool>& materials_transparency, const GEString& material_name,
							   QTextStream& in, const GEString& dir_path) {
	// Default material
	Material mat;
	bool use_transparency = false;
	mat.name() = material_name;

	// Parse file
	QString line;
	bool res = true;
	QString path;
	while ( in.atEnd() == false ) {
		// Get the current line
		line = in.readLine();

		// Split the line
		QStringList list = line.split(" ");
		int count = list.count();
		if ( count < 2 ) continue;
		QStringList::const_iterator it = list.begin();

		// Ambient, diffuse or specular color
		bool state = true, tmp = true;
		if ( ( (*it) == "Ka" ) || ( (*it) == "Kd" ) || ( (*it) == "Ks" ) ) {
			if ( count < 4 ) continue;
			const QString& type = (*it);

			// Read the RGB color components
			ColorRGBA color;
			++it; color.r() = (*it).toFloat(&tmp); state &= tmp;
			++it; color.g() = (*it).toFloat(&tmp); state &= tmp;
			++it; color.b() = (*it).toFloat(&tmp); state &= tmp;
			if ( state != true ) {
				res = false;
				continue;
			}

			// Add the color to the material
			if ( type == "Ka" )
				mat.set_ambient_color(color);
			else if ( type == "Kd" )
				mat.set_color(ColorRGB(color.r(),color.g(),color.b()));
			else if ( type == "Ks" )
				mat.set_specular_color(color);
			continue;
		}

		// Specular coefficient
		if ( (*it) == "Ns" ) {
			// Read the specular coefficient
			++it; float ns = (*it).toFloat(&tmp); state &= tmp;
			if ( state != true ) {
				res = false;
				continue;
			}

			// Add the specular coefficient to the material
			mat.set_shininess(ns);
			continue;
		}

		// Transparency
		if ( ( (*it) == "d" ) || ( (*it) == "Tr" ) ) {
			// Read the transparency
			++it; float d = (*it).toFloat(&tmp); state &= tmp;
			if ( state != true ) {
				res = false;
				continue;
			}

			// Add the transparency to the material
			mat.set_alpha(d);
			continue;
		}

		// Diffuse map
		if ( (*it) == "map_Kd" ) {
			// Read the file path
			++it; path = *it;
			GEString str = dir_path.c_str();
			str += path.toAscii().constData();
			Texture2d texture = Texture2dManager::instance()->get_texture(str);
			if ( texture.initialized() == false ) {
				texture = Texture2d();
				QImage img; img.load(dir_path.c_str() + path);
				texture.bind_texture(true);
				texture.set_parameters(Texture::UseMipmapMode);
				texture.set_parameters(Texture::ModulateMode);
				texture.set_parameters(Texture::RepeatMode);
				texture.set_parameters(Texture::LinearMagFilter);
				texture.set_parameters(Texture::LinearMipmapLinearMinFilter);
				texture.set_texture(img, Texture::RGBAInternalFormat, &use_transparency);
				texture.unbind_texture();
				Texture2dManager::instance()->add_texture(texture,str);
			}

			// Add the texture to the material
			mat.set_texture(&texture);
			continue;
		}

		// New material
		if ( (*it) == "newmtl" ) {
			// Add current material
			materials.push_back( mat );
			materials_transparency.push_back( use_transparency );

			// Read material
			++it;
			return res && load_mtl(materials, materials_transparency, it->toAscii().constData(), in, dir_path);
		}
	}

	// File processed
	materials.push_back( mat );
	materials_transparency.push_back( use_transparency );
	return res;
}

//! Save the mesh to an OBJ file
/*!
 @param mesh The mesh to save
 @param file The path to the OBJ file
 @return True if mesh could be saved, false otherwise
 */
bool TrglMeshIO::save_obj_file(const TrglMesh& mesh, const GEString& file) {
	// Access the file
	QFile f(file.c_str());
	if ( f.open(QIODevice::WriteOnly | QIODevice::Text) == false ) return false;
	QTextStream out(&f);

	// Get directory path
	QString dir_path;
	{{
		// Split the file name
		QStringList list = QString(file.c_str()).split("/");
		if ( list.isEmpty() == false ) {
			QStringList::const_iterator it = list.end(); --it; --it;
			for ( ; it != list.begin(); --it ) {
				dir_path = (*it) + "/" + dir_path;
			}
			dir_path = (*list.begin()) + "/" + dir_path;
		}
	}}

	// Create a map of material files
	std::vector<Material> materials;
	{{
		std::map<GEString,Material> materials_tmp;
		for ( std::vector<TrglMeshGroup>::const_iterator it = mesh.trgl_groups_.begin(); it != mesh.trgl_groups_.end(); ++it ) {
			for ( std::vector<TrglMeshSubGroup>::const_iterator it2 = it->subgroups().begin(); it2 != it->subgroups().end(); ++it2 ) {
				materials_tmp.insert( std::pair<GEString,Material>(it2->material().name(),it2->material()) );
			}
		}

		for ( std::map<GEString,Material>::const_iterator it = materials_tmp.begin(); it != materials_tmp.end(); ++it ) {
			materials.push_back(it->second);
		}
	}}

	// Access the material file
	if ( materials.empty() == false ) {
		QString mfile = file.c_str();
		mfile.replace(".obj", ".mtl");
		QFile mf(mfile);
		if ( mf.open(QIODevice::WriteOnly | QIODevice::Text) == false ) return false;
		QTextStream mout(&mf);

		// Get the material to use, if any
		int name_idx = 1;
		for ( std::vector<Material>::const_iterator it = materials.begin(); it != materials.end(); ++it ) {
			// Create material name
			mf.write("\nnewmtl ");
			GEString name = it->name();
			if ( name.empty() ) {
				QString num; num.setNum(name_idx); ++name_idx;
				name = "Material";
				name += num.toAscii().constData();
			}
			mf.write(name.c_str());
			mf.write("\n");

			// Write material properties
			const Material& mat = *it;
			if ( mat.active_parameter(Material::AmbientMaterial) ) {
				mf.write("Ka ");
				QString num2;
				num2.setNum(mat.ambient_color().r()); mf.write(num2.toAscii().constData()); mf.write(" ");
				num2.setNum(mat.ambient_color().g()); mf.write(num2.toAscii().constData()); mf.write(" ");
				num2.setNum(mat.ambient_color().b()); mf.write(num2.toAscii().constData()); mf.write("\n");
			}
			if ( mat.active_parameter(Material::ColorMaterial) ) {
				mf.write("Kd ");
				QString num2;
				num2.setNum(mat.color().r()); mf.write(num2.toAscii().constData()); mf.write(" ");
				num2.setNum(mat.color().g()); mf.write(num2.toAscii().constData()); mf.write(" ");
				num2.setNum(mat.color().b()); mf.write(num2.toAscii().constData()); mf.write("\n");
			}
			if ( mat.active_parameter(Material::SpecularMaterial) ) {
				mf.write("Ks ");
				QString num2;
				num2.setNum(mat.specular_color().r()); mf.write(num2.toAscii().constData()); mf.write(" ");
				num2.setNum(mat.specular_color().g()); mf.write(num2.toAscii().constData()); mf.write(" ");
				num2.setNum(mat.specular_color().b()); mf.write(num2.toAscii().constData()); mf.write("\n");
			}
			if ( mat.active_parameter(Material::ShininessMaterial) ) {
				mf.write("Ns ");
				QString num2;
				num2.setNum(mat.shininess()); mf.write(num2.toAscii().constData()); mf.write("\n");
			}
			if ( mat.active_parameter(Material::AlphaMaterial) ) {
				mf.write("d ");
				QString num2;
				num2.setNum(mat.alpha()); mf.write(num2.toAscii().constData()); mf.write("\n");
			}
			if ( mat.active_parameter(Material::TexturedMaterial) ) {
				GEString texpath = "";
				if ( Texture2dManager::instance()->get_texture_name(texpath, *(it->texture())) == true ) {
					QString str(texpath.c_str());
					str.replace(dir_path,"");
					mf.write("map_Kd ");
					mf.write(str.toAscii().constData());
					mf.write("\n");
				}
			}
		}
	}

	// Include header
	{{
		f.write("# saved with GameEngine\n\n");
	}}

	// Include material file
	if ( materials.empty() == false ) {
		QString fpath = file.c_str();
		fpath.replace(".obj", ".mtl");
		QStringList list = fpath.split("/");
		if ( list.isEmpty() == false ) {
			QStringList::const_iterator it = list.end();
			--it;
			fpath = *it;
		}
		f.write("mtllib ");
		f.write(fpath.toAscii().constData());
		f.write("\n\n");
	}

	// Get number of vertices, texture coordinates and normals
	size_t nb_vertices = mesh.vertices_.size(), nb_coords = mesh.coords_.size(), nb_normals = mesh.normals_.size();

	// Write the vertices
	for ( size_t k = 0; k < nb_vertices; ++k ) {
		QString txt = "v ", num;
		num.setNum(mesh.vertices_[k].x()); txt += num + " ";
		num.setNum(mesh.vertices_[k].y()); txt += num + " ";
		num.setNum(mesh.vertices_[k].z()); txt += num + "\n";
		f.write(txt.toAscii().constData());
	}

	// Write the texture coordinates
	f.write("\n\n");
	for ( size_t k = 0; k < nb_coords; ++k ) {
		QString txt = "vt ", num;
		num.setNum(mesh.coords_[k].u()); txt += num + " ";
		num.setNum(mesh.coords_[k].v()); txt += num + "\n";
		f.write(txt.toAscii().constData());
	}

	// Write the normals
	f.write("\n\n");
	for ( size_t k = 0; k < nb_normals; ++k ) {
		QString txt = "vn ", num;
		num.setNum(mesh.normals_[k].x()); txt += num + " ";
		num.setNum(mesh.normals_[k].y()); txt += num + " ";
		num.setNum(mesh.normals_[k].z()); txt += num + "\n";
		f.write(txt.toAscii().constData());
	}

	// Write the faces
	f.write("\n\n");
	for ( std::vector<TrglMeshGroup>::const_iterator it = mesh.trgl_groups_.begin(); it != mesh.trgl_groups_.end(); ++it ) {
		// Write the group name
		f.write("g ");
		f.write(it->name().c_str());
		f.write("\n\n");

		// Write the subgroups
		for ( std::vector<TrglMeshSubGroup>::const_iterator it2 = it->subgroups().begin(); it2 != it->subgroups().end(); ++it2 ) {
			f.write("usemtl ");
			f.write(it2->material().name().c_str());
			f.write("\n");

			// Write the subgroup faces
			for ( std::vector<IndexedTrgl>::const_iterator it3 = it2->triangles().begin(); it3 != it2->triangles().end(); ++it3 ) {
				// Face
				QString txt = "f ", num;

				// First vertex
				num.setNum(it3->index1()+1); txt += num;
				if ( it3->has_texture_coordinates() == true ) {
					txt += "/";
					num.setNum(it3->cindex1()+1);
					txt += num;
				}
				if ( it3->has_normals() == true ) {
					if ( it3->has_texture_coordinates() == false ) txt += "/";
					txt += "/";
					num.setNum(it3->nindex1()+1);
					txt += num;
				}
				txt += " ";

				// Second vertex
				num.setNum(it3->index2()+1); txt += num;
				if ( it3->has_texture_coordinates() == true ) {
					txt += "/";
					num.setNum(it3->cindex2()+1);
					txt += num;
				}
				if ( it3->has_normals() == true ) {
					if ( it3->has_texture_coordinates() == false ) txt += "/";
					txt += "/";
					num.setNum(it3->nindex2()+1);
					txt += num;
				}
				txt += " ";

				// Third vertex
				num.setNum(it3->index3()+1); txt += num;
				if ( it3->has_texture_coordinates() == true ) {
					txt += "/";
					num.setNum(it3->cindex3()+1);
					txt += num;
				}
				if ( it3->has_normals() == true ) {
					if ( it3->has_texture_coordinates() == false ) txt += "/";
					txt += "/";
					num.setNum(it3->nindex3()+1);
					txt += num;
				}
				txt += "\n";

				// Write face
				f.write(txt.toAscii().constData());
			}
		}
	}

	return true;
}

//! Load the mesh from a binary file
/*!
 @param mesh The mesh to load
 @param file The path to the binary file
 @param scale Rescale the vertex positions of the mesh
 @return True if mesh could be loaded, false otherwise
 */
bool TrglMeshIO::load_bin_file(TrglMesh& mesh, const GEString& file, float scale) {
	// Clear the mesh
	mesh.vertices_.clear();
	mesh.normals_.clear();
	mesh.coords_.clear();
	mesh.trgl_groups_.clear();

	// Access the file
	FileIO f(file.c_str(), FileIO::ReadBinary);
	if ( f.open_file() == false ) {
		f.close_file();
		return false;
	}

	// Get directory path
	GEString dir_path;
	{{
		// Split the file name
		QStringList list = QString(file.c_str()).split("/");
		if ( list.isEmpty() == false ) {
			QStringList::const_iterator it = list.end(); --it; --it;
			for ( ; it != list.begin(); --it ) {
				dir_path = GEString(it->toAscii().constData()) + "/" + dir_path;
			}
			dir_path = GEString(list.begin()->toAscii().constData()) + "/" + dir_path;
		}
	}}

	// Read number of vertices, texture coordinates and normals
	size_t nb_vertices = 0, nb_coords = 0, nb_normals = 0;
	f.read_element(nb_vertices); mesh.vertices_.resize(nb_vertices);
	f.read_element(nb_coords); mesh.coords_.resize(nb_coords);
	f.read_element(nb_normals); mesh.normals_.resize(nb_normals);

	// Read number of triangle groups
	size_t nb_groups = 0;
	f.read_element(nb_groups); mesh.trgl_groups_.resize(nb_groups);

	// Write number of materials
	size_t nb_materials = 0;
	f.read_element(nb_materials);

	// Read the vertices
	for ( size_t k = 0; k < nb_vertices; ++k ) {
		f.read_element(mesh.vertices_[k].x()); mesh.vertices_[k].x() *= scale;
		f.read_element(mesh.vertices_[k].y()); mesh.vertices_[k].y() *= scale;
		f.read_element(mesh.vertices_[k].z()); mesh.vertices_[k].z() *= scale;
	}

	// Read the texture coordinates
	for ( size_t k = 0; k < nb_coords; ++k ) {
		f.read_element(mesh.coords_[k].u());
		f.read_element(mesh.coords_[k].v());
	}

	// Read the normals
	for ( size_t k = 0; k < nb_normals; ++k ) {
		f.read_element(mesh.normals_[k].x());
		f.read_element(mesh.normals_[k].y());
		f.read_element(mesh.normals_[k].z());
	}

	// Read the materials
	std::vector<Material> materials;
	std::vector<bool> materials_transparency;
	for ( size_t k = 0; k < nb_materials; ++k ) {
		// Read the material properties
		Material mat;
		bool use_transparency = false;

		// Read the material name
		{{
			// Read the material name
			int len = 0;
			f.read_element(len);
			GEString name = "";
			for ( int l = 0; l < len; ++l ) {
				char c;
				f.read_element(c);
				name += c;
			}
			mat.name() = name;
		}}

		// Ambient color
		bool ambient = false;
		f.read_element(ambient);
		if ( ambient == true ) {
			ColorRGBA color;
			f.read_element(color.r());
			f.read_element(color.g());
			f.read_element(color.b());
			mat.set_ambient_color(color);
		}
		else mat.set_active_parameter(Material::AmbientMaterial, false);

		// Diffuse color
		bool diffuse = false;
		f.read_element(diffuse);
		if ( diffuse == true ) {
			ColorRGB color;
			f.read_element(color.r());
			f.read_element(color.g());
			f.read_element(color.b());
			mat.set_color(color);
		}
		else mat.set_active_parameter(Material::ColorMaterial, false);

		// Specular color
		bool specular = false;
		f.read_element(specular);
		if ( specular == true ) {
			ColorRGBA color;
			f.read_element(color.r());
			f.read_element(color.g());
			f.read_element(color.b());
			mat.set_specular_color(color);
		}
		else mat.set_active_parameter(Material::SpecularMaterial, false);

		// Specular coefficient
		bool specular_coeff = false;
		f.read_element(specular_coeff);
		if ( specular_coeff == true ) {
			float coeff = 10.0f;
			f.read_element(coeff);
			mat.set_shininess(coeff);
		}
		else mat.set_active_parameter(Material::ShininessMaterial, false);

		// Alpha coefficient
		bool alpha = false;
		f.read_element(alpha);
		if ( alpha == true ) {
			float coeff = 1.0f;
			f.read_element(coeff);
			mat.set_alpha(coeff);
		}
		else mat.set_active_parameter(Material::AlphaMaterial, false);

		// Texture
		bool textured = false;
		f.read_element(textured);
		if ( textured == true ) {
			// Read the material name
			int len = 0;
			f.read_element(len);
			GEString name = "";
			for ( int l = 0; l < len; ++l ) {
				char c;
				f.read_element(c);
				name += c;
			}

			// Read the file path
			Texture2d texture = Texture2dManager::instance()->get_texture(dir_path.c_str() + name);
			if ( texture.initialized() == false ) {
				texture = Texture2d();
				QString path = dir_path.c_str();
				path += name.c_str();
				QImage img; img.load(path);
				texture.bind_texture(true);
				texture.set_parameters(Texture::UseMipmapMode);
				texture.set_parameters(Texture::ModulateMode);
				texture.set_parameters(Texture::RepeatMode);
				texture.set_parameters(Texture::LinearMagFilter);
				texture.set_parameters(Texture::LinearMipmapLinearMinFilter);
				texture.set_texture(img, Texture::RGBAInternalFormat, &use_transparency);
				texture.unbind_texture();
				Texture2dManager::instance()->add_texture(texture,dir_path.c_str() + name);
			}

			// Add the texture to the material
			mat.set_texture(&texture);
		}
		else mat.set_active_parameter(Material::TexturedMaterial, false);

		// Add the material
		materials_transparency.push_back(use_transparency);
		materials.push_back(mat);
	}

	// Read the triangle groups
	for ( size_t k = 0; k < nb_groups; ++k ) {
		// Read the number of subgroups
		size_t nb_subgroups = 0;
		f.read_element(nb_subgroups); mesh.trgl_groups_[k].subgroups().resize(nb_subgroups);
		std::vector<TrglMeshSubGroup>& subgroups = mesh.trgl_groups_[k].subgroups();

		// Read the triangle subgroups
		for ( size_t l = 0; l < nb_subgroups; ++l ) {
			// Write the number of triangles
			size_t nb_trgls = 0;
			f.read_element(nb_trgls); subgroups[l].triangles().resize(nb_trgls);

			// Read the material name
			int len = 0;
			f.read_element(len);
			GEString name = "";
			for ( int m = 0; m < len; ++m ) {
				char c;
				f.read_element(c);
				name += c;
			}

			// Read the material properties
			Material mat;
			bool tmp = false;
			int midx = -1;
			for ( std::vector<Material>::const_iterator mit = materials.begin(); mit != materials.end(); ++mit ) {
				++midx;
				if ( name != mit->name() ) continue;
				mat = *mit;
				tmp = true;
				break;
			}
			if ( tmp == true ) {
				subgroups[l].material() = mat;
				subgroups[l].use_transparency() = materials_transparency[midx];
			}

			// Read the faces
			std::vector<IndexedTrgl>& trgls = subgroups[l].triangles();
			for ( size_t m = 0; m < nb_trgls; ++m ) {
				// Vertex indexes
				f.read_element(trgls[m].index1());
				f.read_element(trgls[m].index2());
				f.read_element(trgls[m].index3());

				// Texture indexes
				bool use_coords = false;
				f.read_element(use_coords);
				trgls[m].use_texture_coordinates(use_coords);
				if ( use_coords == true ) {
					f.read_element(trgls[m].cindex1());
					f.read_element(trgls[m].cindex2());
					f.read_element(trgls[m].cindex3());
				}

				// Normal indexes
				bool use_normals = false;
				f.read_element(use_normals);
				trgls[m].use_normals(use_normals);
				if ( use_normals == true ) {
					f.read_element(trgls[m].nindex1());
					f.read_element(trgls[m].nindex2());
					f.read_element(trgls[m].nindex3());
				}
			}
		}
	}

	// Close binary file
	return f.close_file();
}

//! Save the mesh to a binary file
/*!
 @param mesh The mesh to save
 @param file The path to the binary file
 @return True if mesh could be saved, false otherwise
 */
bool TrglMeshIO::save_bin_file(const TrglMesh& mesh, const GEString& file) {
	// Access the file
	FileIO f(file.c_str(), FileIO::WriteBinary);
	if ( f.open_file() == false ) {
		f.close_file();
		return false;
	}

	// Get directory path
	QString dir_path;
	{{
		// Split the file name
		QStringList list = QString(file.c_str()).split("/");
		if ( list.isEmpty() == false ) {
			QStringList::const_iterator it = list.end(); --it; --it;
			for ( ; it != list.begin(); --it ) {
				dir_path = (*it) + "/" + dir_path;
			}
			dir_path = (*list.begin()) + "/" + dir_path;
		}
	}}

	// Write number of vertices, texture coordinates and normals
	size_t nb_vertices = mesh.vertices_.size(), nb_coords = mesh.coords_.size(), nb_normals = mesh.normals_.size();
	f.write_element(nb_vertices);
	f.write_element(nb_coords);
	f.write_element(nb_normals);

	// Write number of triangle groups
	size_t nb_groups = mesh.trgl_groups_.size();
	f.write_element(nb_groups);

	// Create a map of material files
	std::vector<Material> materials;
	{{
		std::map<GEString,Material> materials_tmp;
		for ( std::vector<TrglMeshGroup>::const_iterator it = mesh.trgl_groups_.begin(); it != mesh.trgl_groups_.end(); ++it ) {
			for ( std::vector<TrglMeshSubGroup>::const_iterator it2 = it->subgroups().begin(); it2 != it->subgroups().end(); ++it2 ) {
				materials_tmp.insert( std::pair<GEString,Material>(it2->material().name(),it2->material()) );
			}
		}

		for ( std::map<GEString,Material>::const_iterator it = materials_tmp.begin(); it != materials_tmp.end(); ++it ) {
			materials.push_back(it->second);
		}
	}}

	// Write number of materials
	size_t nb_materials = materials.size();
	f.write_element(nb_materials);

	// Write the vertices
	for ( size_t k = 0; k < nb_vertices; ++k ) {
		f.write_element(mesh.vertices_[k].x());
		f.write_element(mesh.vertices_[k].y());
		f.write_element(mesh.vertices_[k].z());
	}

	// Write the texture coordinates
	for ( size_t k = 0; k < nb_coords; ++k ) {
		f.write_element(mesh.coords_[k].u());
		f.write_element(mesh.coords_[k].v());
	}

	// Write the normals
	for ( size_t k = 0; k < nb_normals; ++k ) {
		f.write_element(mesh.normals_[k].x());
		f.write_element(mesh.normals_[k].y());
		f.write_element(mesh.normals_[k].z());
	}

	// Write the materials
	long midx = 0;
	for ( std::vector<Material>::const_iterator it = materials.begin(); it != materials.end(); ++it ) {
		// Write the material properties
		const Material& mat = *it;

		// Write the material name
		{{
			const char* name = mat.name().c_str();
			int len = (int)strlen(name);
			f.write_element(len+1);
			for ( int l = 0; l < len; ++l ) {
				f.write_element(name[l]);
			}
			f.write_element('\0');
		}}

		// Ambient color
		bool ambient = mat.active_parameter(Material::AmbientMaterial);
		f.write_element(ambient);
		if ( ambient == true ) {
			f.write_element(mat.ambient_color().r());
			f.write_element(mat.ambient_color().g());
			f.write_element(mat.ambient_color().b());
		}

		// Diffuse color
		bool diffuse = mat.active_parameter(Material::ColorMaterial);
		f.write_element(diffuse);
		if ( diffuse == true ) {
			f.write_element(mat.color().r());
			f.write_element(mat.color().g());
			f.write_element(mat.color().b());
		}

		// Specular color
		bool specular = mat.active_parameter(Material::SpecularMaterial);
		f.write_element(specular);
		if ( specular == true ) {
			f.write_element(mat.specular_color().r());
			f.write_element(mat.specular_color().g());
			f.write_element(mat.specular_color().b());
		}

		// Specular coefficient
		bool specular_coeff = mat.active_parameter(Material::ShininessMaterial);
		f.write_element(specular_coeff);
		if ( specular_coeff == true ) {
			f.write_element(mat.shininess());
		}

		// Alpha coefficient
		bool alpha = mat.active_parameter(Material::AlphaMaterial);
		f.write_element(alpha);
		if ( alpha == true ) {
			f.write_element(mat.alpha());
		}

		// Write the material texture
		bool textured = mat.active_parameter(Material::TexturedMaterial);
		int test = textured;
		f.write_element(textured);
		if ( textured == true ) {
			GEString texpath = "";
			if ( Texture2dManager::instance()->get_texture_name(texpath, *(mat.texture())) == true ) {
				QString str(texpath.c_str());
				str.replace(dir_path,"");
				texpath = str.toAscii().constData();
				const char* name = texpath.c_str();
				int len = (int)strlen(name);
				f.write_element(len+1);
				for ( int l = 0; l < len; ++l ) {
					f.write_element(name[l]);
				}
				f.write_element('\0');
			}
		}
	}

	// Write the triangle groups
	for ( size_t k = 0; k < nb_groups; ++k ) {
		// Write the number of subgroups
		const std::vector<TrglMeshSubGroup>& subgroups = mesh.trgl_groups_[k].subgroups();
		size_t nb_subgroups = subgroups.size();
		f.write_element(nb_subgroups);

		// Write the triangle subgroups
		for ( size_t l = 0; l < nb_subgroups; ++l ) {
			// Write the number of triangles
			size_t nb_trgls = subgroups[l].triangles().size();
			f.write_element(nb_trgls);

			// Write the material name
			const char* name = subgroups[l].material().name().c_str();
			int len = (int)strlen(name);
			f.write_element(len+1);
			for ( int m = 0; m < len; ++m ) {
				f.write_element(name[m]);
			}
			f.write_element('\0');

			// Write the faces
			const std::vector<IndexedTrgl>& trgls = subgroups[l].triangles();
			for ( size_t m = 0; m < nb_trgls; ++m ) {
				// Vertex indexes
				f.write_element(trgls[m].index1());
				f.write_element(trgls[m].index2());
				f.write_element(trgls[m].index3());

				// Texture indexes
				bool use_coords = trgls[m].has_texture_coordinates();
				f.write_element(use_coords);
				if ( use_coords == true ) {
					f.write_element(trgls[m].cindex1());
					f.write_element(trgls[m].cindex2());
					f.write_element(trgls[m].cindex3());
				}

				// Normal indexes
				bool use_normals = trgls[m].has_normals();
				f.write_element(use_normals);
				if ( use_normals == true ) {
					f.write_element(trgls[m].nindex1());
					f.write_element(trgls[m].nindex2());
					f.write_element(trgls[m].nindex3());
				}
			}
		}
	}

	// Close binary file
	return f.close_file();
}

//! Load the mesh from a NIF file (NetImmerse File)
/*!
 *  This function uses the Niflib library to load the NIF file, provided with the following license:
 *
 @verbatim
  Copyright (c) 2005, NIF File Format Library and Tools
  All rights reserved.

  Redistribution and use in source and binary forms, with or without
  modification, are permitted provided that the following conditions
  are met:

     * Redistributions of source code must retain the above copyright
       notice, this list of conditions and the following disclaimer.

     * Redistributions in binary form must reproduce the above
       copyright notice, this list of conditions and the following
       disclaimer in the documentation and/or other materials provided
       with the distribution.

     * Neither the name of the NIF File Format Library and Tools
       project nor the names of its contributors may be used to endorse
       or promote products derived from this software without specific
       prior written permission.

  THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
  "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
  LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS
  FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE
  COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT,
  INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING,
  BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
  LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
  CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
  LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN
  ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
  POSSIBILITY OF SUCH DAMAGE. @endverbatim
 @param mesh The mesh to load
 @param file The path to the NIF file
 @param scale Rescale the vertex positions of the mesh
 @return True if mesh could be loaded, false otherwise
 */
bool TrglMeshIO::load_nif_file(TrglMesh& mesh, const GEString& file, float scale) {
	// Clear the mesh
	mesh.vertices_.clear();
	mesh.normals_.clear();
	mesh.coords_.clear();
	mesh.trgl_groups_.clear();

	// Read the objects from the NIF file
	std::vector<Niflib::NiObjectRef> objects = Niflib::ReadNifList(file);

	// Get directory path
	GEString dir_path;
	{{
		// Split the file name
		QStringList list = QString(file.c_str()).split("/");
		if ( list.isEmpty() == false ) {
			QStringList::const_iterator it = list.end(); --it; --it;
			for ( ; it != list.begin(); --it ) {
				dir_path = GEString(it->toAscii().constData()) + "/" + dir_path;
			}
			dir_path = GEString(list.begin()->toAscii().constData()) + "/" + dir_path;
		}
	}}

	// Try to find the triangles data
	int group_index = 0;
	for ( std::vector<Niflib::NiObjectRef>::const_iterator it = objects.begin(); it != objects.end(); ++it ) {
		// Find triangle strip data
		if ( (*it)->IsDerivedType(Niflib::NiTriStrips::TYPE) == false ) continue;
		Niflib::NiTriStripsRef strips = Niflib::DynamicCast<Niflib::NiTriStrips>( *it );

		// Get material properties
		Material mat;
		bool use_transparency = false;
		std::vector<Niflib::NiPropertyRef> properties = strips->GetProperties();
		for ( std::vector<Niflib::NiPropertyRef>::const_iterator pit = properties.begin(); pit != properties.end(); ++pit ) {
			// Material properties
			if ( (*pit)->IsDerivedType(Niflib::NiMaterialProperty::TYPE) == true ) {
				// Cast the material property
				Niflib::NiMaterialPropertyRef prop = Niflib::DynamicCast<Niflib::NiMaterialProperty>( *pit );

				// Get the material name
				mat.name() = prop->GetName();

				// Get the material ambient color
				Niflib::Color3 ambient = prop->GetAmbientColor();
				mat.set_ambient_color( ColorRGBA(ambient.r,ambient.g,ambient.b,1.0f) );

				// Get the material diffuse color
				Niflib::Color3 diffuse = prop->GetDiffuseColor();
				mat.set_diffuse_color( ColorRGBA(diffuse.r,diffuse.g,diffuse.b,1.0f) );

				// Get the material specular color
				Niflib::Color3 specular = prop->GetSpecularColor();
				mat.set_specular_color( ColorRGBA(specular.r,specular.g,specular.b,1.0f) );

				// Get the material emission color
				Niflib::Color3 emission = prop->GetEmissiveColor();
				mat.set_emission_color( ColorRGBA(emission.r,emission.g,emission.b,1.0f) );

				// Get the material transparency
				float transparency = prop->GetTransparency();
				mat.set_alpha( transparency );
			}

			// Texturing properties
			if ( (*pit)->IsDerivedType(Niflib::NiTexturingProperty::TYPE) == true ) {
				// Cast the texturing property
				Niflib::NiTexturingPropertyRef prop = Niflib::DynamicCast<Niflib::NiTexturingProperty>( *pit );

				// Get the texture file
				if ( prop->HasTexture(0) ) {
					Niflib::TexDesc desc = prop->GetTexture(0);
					GEString filename = desc.source->GetTextureFileName();

					// Setup texture
					Texture2d texture = Texture2dManager::instance()->get_texture(dir_path.c_str() + filename);
					if ( texture.initialized() == false ) {
						texture = Texture2d();
						GEString path = dir_path + filename;
						QImage img; img.load(path.c_str());
						texture.bind_texture(true);
						texture.set_parameters(Texture::UseMipmapMode);
						texture.set_parameters(Texture::ModulateMode);
						texture.set_parameters(Texture::RepeatMode);
						texture.set_parameters(Texture::LinearMagFilter);
						texture.set_parameters(Texture::LinearMipmapLinearMinFilter);
						texture.set_texture(img, Texture::RGBAInternalFormat, &use_transparency);
						texture.unbind_texture();
						Texture2dManager::instance()->add_texture(texture,dir_path.c_str() + filename);
					}

					// Add the texture to the material
					mat.set_texture(&texture);
				}
			}
		}

		// Get strips data
		Niflib::NiTriStripsDataRef data = Niflib::DynamicCast<Niflib::NiTriStripsData>( strips->GetData() );

		// Add the vertices
		SurfaceMesh::VertexIndex vcount = (SurfaceMesh::VertexIndex)mesh.vertices_.size();
		{{
			std::vector<Niflib::Vector3> vertices = data->GetVertices();
			Point3d<float> vrtx;
			for ( std::vector<Niflib::Vector3>::const_iterator vit = vertices.begin(); vit != vertices.end(); ++vit ) {
				const Niflib::Vector3& vertex = *vit;
				vrtx.x() = vit->x * scale; vrtx.y() = vit->y * scale; vrtx.z() = vit->z * scale;
				mesh.vertices_.push_back( vrtx );
			}
		}}

		// Add the normals
		SurfaceMesh::NormalIndex ncount = (SurfaceMesh::NormalIndex)mesh.normals_.size();
		{{
			std::vector<Niflib::Vector3> normals = data->GetNormals();
			Vector3d<float> norm;
			for ( std::vector<Niflib::Vector3>::const_iterator nit = normals.begin(); nit != normals.end(); ++nit ) {
				const Niflib::Vector3& normal = *nit;
				norm.x() = nit->x; norm.y() = nit->y; norm.z() = nit->z;
				mesh.normals_.push_back( norm );
			}
		}}

		// Add the texture coordinates
		SurfaceMesh::TextureCoordinateIndex ccount = (SurfaceMesh::TextureCoordinateIndex)mesh.coords_.size();
		{{
			short uvcount = data->GetUVSetCount();
			for ( int k = 0; k < uvcount; ++k ) {
				std::vector<Niflib::TexCoord> coords = data->GetUVSet(k);
				TextureCoordinate2d coord;
				for ( std::vector<Niflib::TexCoord>::const_iterator tit = coords.begin(); tit != coords.end(); ++tit ) {
					const Niflib::TexCoord& c = *tit;
					coord.u() = tit->u; coord.v() = tit->v;
					mesh.coords_.push_back( coord );
				}
			}
		}}

		// Setup the triangle group
		TrglMeshGroup group;
		group.name() = strips->GetName();

		// Setup the triangle subgroup
		TrglMeshSubGroup subgroup;
		subgroup.material() = mat;
		subgroup.use_transparency() = use_transparency;

		// Add the triangle strips
		{{
			// Prepare triangle
			IndexedTrgl trgl;
			trgl.use_texture_coordinates( mesh.coords_.empty() == false );
			trgl.use_normals( mesh.normals_.empty() == false );

			// Add triangles
			std::vector<Niflib::Triangle> trgls = data->GetTriangles();
			for ( std::vector<Niflib::Triangle>::const_iterator tit = trgls.begin(); tit != trgls.end(); ++tit ) {
				// Setup triangle vertices
				{{
					SurfaceMesh::VertexIndex v1 = (SurfaceMesh::VertexIndex)tit->v1; v1 += vcount;
					SurfaceMesh::VertexIndex v2 = (SurfaceMesh::VertexIndex)tit->v2; v2 += vcount;
					SurfaceMesh::VertexIndex v3 = (SurfaceMesh::VertexIndex)tit->v3; v3 += vcount;
					trgl.index1() = v1;
					trgl.index2() = v2;
					trgl.index3() = v3;
				}}

				// Setup triangle texture coordinates
				if ( trgl.has_texture_coordinates() ) {
					SurfaceMesh::TextureCoordinateIndex cv1 = (SurfaceMesh::TextureCoordinateIndex)tit->v1; cv1 += ccount;
					SurfaceMesh::TextureCoordinateIndex cv2 = (SurfaceMesh::TextureCoordinateIndex)tit->v2; cv2 += ccount;
					SurfaceMesh::TextureCoordinateIndex cv3 = (SurfaceMesh::TextureCoordinateIndex)tit->v3; cv3 += ccount;
					trgl.cindex1() = cv1;
					trgl.cindex2() = cv2;
					trgl.cindex3() = cv3;
				}

				// Setup triangle texture normals
				if ( trgl.has_normals() ) {
					SurfaceMesh::NormalIndex nv1 = (SurfaceMesh::NormalIndex)tit->v1; nv1 += ncount;
					SurfaceMesh::NormalIndex nv2 = (SurfaceMesh::NormalIndex)tit->v2; nv2 += ncount;
					SurfaceMesh::NormalIndex nv3 = (SurfaceMesh::NormalIndex)tit->v3; nv3 += ncount;
					trgl.nindex1() = nv1;
					trgl.nindex2() = nv2;
					trgl.nindex3() = nv3;
				}

				// Add the triangle
				subgroup.triangles().push_back(trgl);
			}
		}}
		group.subgroups().push_back(subgroup);
		mesh.trgl_groups_.push_back(group);
	}
	return true;
}

//! Save the mesh to a NIF file (NetImmerse File)
/*!
 *  This function uses the Niflib library to save the NIF file, provided with the following license:
 *
 @verbatim
  Copyright (c) 2005, NIF File Format Library and Tools
  All rights reserved.

  Redistribution and use in source and binary forms, with or without
  modification, are permitted provided that the following conditions
  are met:

     * Redistributions of source code must retain the above copyright
       notice, this list of conditions and the following disclaimer.

     * Redistributions in binary form must reproduce the above
       copyright notice, this list of conditions and the following
       disclaimer in the documentation and/or other materials provided
       with the distribution.

     * Neither the name of the NIF File Format Library and Tools
       project nor the names of its contributors may be used to endorse
       or promote products derived from this software without specific
       prior written permission.

  THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
  "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
  LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS
  FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE
  COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT,
  INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING,
  BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
  LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
  CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
  LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN
  ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
  POSSIBILITY OF SUCH DAMAGE. @endverbatim
 @param mesh The mesh to save
 @param file The path to the NIF file
 @return True if mesh could be saved, false otherwise
 */
bool TrglMeshIO::save_nif_file(const TrglMesh& mesh, const GEString& file) {

	//==============================================================

	// Create the NIF tree root
	Niflib::NiNodeRef node = Niflib::DynamicCast<Niflib::NiNode>( Niflib::NiNode::Create() );
	node->SetName("Scene Root");
	node->SetFlags( 14 );

	//==============================================================

	// Get directory path
	GEString dir_path;
	{{
		// Split the file name
		QStringList list = QString(file.c_str()).split("/");
		if ( list.isEmpty() == false ) {
			QStringList::const_iterator it = list.end(); --it; --it;
			for ( ; it != list.begin(); --it ) {
				dir_path = GEString(it->toAscii().constData()) + "/" + dir_path;
			}
			dir_path = GEString(list.begin()->toAscii().constData()) + "/" + dir_path;
		}
	}}

	// Add the triangle groups
	for ( std::vector<TrglMeshGroup>::const_iterator it = mesh.trgl_groups_.begin(); it != mesh.trgl_groups_.end(); ++it ) {
		// Write the subgroups
		int subgroupidx = 0;
		for ( std::vector<TrglMeshSubGroup>::const_iterator it2 = it->subgroups().begin(); it2 != it->subgroups().end(); ++it2 ) {
			// Get strips name
			GEString name = it->name();
			if ( subgroupidx > 0 ) {
				QString num; num.setNum( subgroupidx );
				name += "_";
				name += num.toAscii().constData();
			}
			++subgroupidx;

			// Create triangle strips
			Niflib::NiTriStripsRef strips = Niflib::DynamicCast<Niflib::NiTriStrips>( Niflib::NiTriStrips::Create() );
			strips->SetName( name );

			//==============================================================

			// Material texturing property
			const Material& mat = it2->material();
			if ( mat.active_parameter(Material::TexturedMaterial) ) {
				// Create material texturing property
				Niflib::NiTexturingPropertyRef texture = Niflib::DynamicCast<Niflib::NiTexturingProperty>( Niflib::NiTexturingProperty::Create() );

				// Get texture filename
				GEString texpath = "";
				if ( Texture2dManager::instance()->get_texture_name(texpath, *(mat.texture())) == true ) {
					QString str(texpath.c_str());
					str.replace(dir_path.c_str(),"");
					texpath = str.toAscii().constData();
				}

				// Create descriptor
				Niflib::TexDesc desc;
				desc.source = Niflib::DynamicCast<Niflib::NiSourceTexture>( Niflib::NiSourceTexture::Create() );
				desc.source->SetExternalTexture(texpath);

				// Add descriptor
				texture->SetTexture(0,desc);

				// Add property
				strips->AddProperty( texture );
			}

			//==============================================================

			// Material property
			{{
				// Create material property
				Niflib::NiMaterialPropertyRef prop = Niflib::DynamicCast<Niflib::NiMaterialProperty>( Niflib::NiMaterialProperty::Create() );

				// Ambient color
				if ( mat.active_parameter(Material::AmbientMaterial) ) {
					Niflib::Color3 color;
					color.r = mat.ambient_color().r();
					color.g = mat.ambient_color().g();
					color.b = mat.ambient_color().b();
					prop->SetAmbientColor(color);
				}

				// Diffuse color
				if ( mat.active_parameter(Material::DiffuseMaterial) ) {
					Niflib::Color3 color;
					color.r = mat.diffuse_color().r();
					color.g = mat.diffuse_color().g();
					color.b = mat.diffuse_color().b();
					prop->SetDiffuseColor(color);
				}

				// Specular color
				if ( mat.active_parameter(Material::SpecularMaterial) ) {
					Niflib::Color3 color;
					color.r = mat.specular_color().r();
					color.g = mat.specular_color().g();
					color.b = mat.specular_color().b();
					prop->SetSpecularColor(color);
				}

				// Emissive color
				if ( mat.active_parameter(Material::EmissionMaterial) ) {
					Niflib::Color3 color;
					color.r = mat.emission_color().r();
					color.g = mat.emission_color().g();
					color.b = mat.emission_color().b();
					prop->SetEmissiveColor(color);
				}

				// Shininess
				if ( mat.active_parameter(Material::ShininessMaterial) ) {
					prop->SetGlossiness(mat.shininess());
				}

				// Transparency
				if ( mat.active_parameter(Material::AlphaMaterial) ) {
					prop->SetTransparency(mat.alpha());
				}

				// Add property
				strips->AddProperty( prop );
			}}

			//==============================================================

			// Create vertex/normals/texture coordinates list
			std::vector< Niflib::Vector3 > vertices, normals;
			std::vector< Niflib::TexCoord > coords;
			std::map<int,int> vmap;

			// Create triangles
			std::vector< Niflib::Triangle > trgls;

			// Find normals and texture coordinates flags
			bool has_normals = false, has_texture_coordinates = false;
			for ( std::vector<IndexedTrgl>::const_iterator tit = it2->triangles().begin(); tit != it2->triangles().end(); ++tit ) {
				if ( has_normals && has_texture_coordinates ) break;

				// Test normals flag
				if ( has_normals == false ) {
					if ( tit->has_normals() ) has_normals = true;
				}

				// Test texture coordinates flag
				if ( has_texture_coordinates == false ) {
					if ( tit->has_texture_coordinates() ) has_texture_coordinates = true;
				}
			}

			// Add triangles
			for ( std::vector<IndexedTrgl>::const_iterator tit = it2->triangles().begin(); tit != it2->triangles().end(); ++tit ) {
				// Get the current triangle
				const IndexedTrgl& trgl = *tit;

				// Compute indexes
				SurfaceMesh::VertexIndex v1 = 0, v2 = 0, v3 = 0;
				{{
					SurfaceMesh::VertexIndex v = trgl.index1();
					SurfaceMesh::NormalIndex n = trgl.nindex1();
					SurfaceMesh::TextureCoordinateIndex c = trgl.cindex1();
					std::map<int,int>::const_iterator mvit = vmap.find(v);
					if ( mvit != vmap.end() ) v = mvit->second;
					else {
						// Add vertex
						int idx = (int)vertices.size();
						Niflib::Vector3 t;
						t.x = mesh.vertices_[v].x();
						t.y = mesh.vertices_[v].y();
						t.z = mesh.vertices_[v].z();
						vertices.push_back( t );
						vmap.insert( std::pair<int,int>(v,idx) );
						v = idx;

						// Add normal
						if ( has_normals ) {
							Niflib::Vector3 norm;
							if ( n < (SurfaceMesh::NormalIndex)mesh.normals_.size() ) {
								norm.x = mesh.normals_[n].x();
								norm.y = mesh.normals_[n].y();
								norm.z = mesh.normals_[n].z();
							}
							else {
								norm.x = 0;
								norm.y = 0;
								norm.z = 0;
							}
							normals.push_back( norm );
						}

						// Add texture coordinate
						if ( has_texture_coordinates ) {
							Niflib::TexCoord coord;
							if ( c < (SurfaceMesh::TextureCoordinateIndex)mesh.coords_.size() ) {
								coord.u = mesh.coords_[c].u();
								coord.v = mesh.coords_[c].v();
							}
							else {
								coord.u = 0;
								coord.v = 0;
							}
							coords.push_back( coord );
						}
					}
					v1 = v;

					v = trgl.index2();
					n = trgl.nindex2();
					c = trgl.cindex2();
					mvit = vmap.find(v);
					if ( mvit != vmap.end() ) v = mvit->second;
					else {
						// Add vertex
						int idx = (int)vertices.size();
						Niflib::Vector3 t;
						t.x = mesh.vertices_[v].x();
						t.y = mesh.vertices_[v].y();
						t.z = mesh.vertices_[v].z();
						vertices.push_back( t );
						vmap.insert( std::pair<int,int>(v,idx) );
						v = idx;

						// Add normal
						if ( has_normals ) {
							Niflib::Vector3 norm;
							if ( n < (SurfaceMesh::NormalIndex)mesh.normals_.size() ) {
								norm.x = mesh.normals_[n].x();
								norm.y = mesh.normals_[n].y();
								norm.z = mesh.normals_[n].z();
							}
							else {
								norm.x = 0;
								norm.y = 0;
								norm.z = 0;
							}
							normals.push_back( norm );
						}

						// Add texture coordinate
						if ( has_texture_coordinates ) {
							Niflib::TexCoord coord;
							if ( c < (SurfaceMesh::TextureCoordinateIndex)mesh.coords_.size() ) {
								coord.u = mesh.coords_[c].u();
								coord.v = mesh.coords_[c].v();
							}
							else {
								coord.u = 0;
								coord.v = 0;
							}
							coords.push_back( coord );
						}
					}
					v2 = v;

					v = trgl.index3();
					n = trgl.nindex3();
					c = trgl.cindex3();
					mvit = vmap.find(v);
					if ( mvit != vmap.end() ) v = mvit->second;
					else {
						// Add vertex
						int idx = (int)vertices.size();
						Niflib::Vector3 t;
						t.x = mesh.vertices_[v].x();
						t.y = mesh.vertices_[v].y();
						t.z = mesh.vertices_[v].z();
						vertices.push_back( t );
						vmap.insert( std::pair<int,int>(v,idx) );
						v = idx;

						// Add normal
						if ( has_normals ) {
							Niflib::Vector3 norm;
							if ( n < (SurfaceMesh::NormalIndex)mesh.normals_.size() ) {
								norm.x = mesh.normals_[n].x();
								norm.y = mesh.normals_[n].y();
								norm.z = mesh.normals_[n].z();
							}
							else {
								norm.x = 0;
								norm.y = 0;
								norm.z = 0;
							}
							normals.push_back( norm );
						}

						// Add texture coordinate
						if ( has_texture_coordinates ) {
							Niflib::TexCoord coord;
							if ( c < (SurfaceMesh::TextureCoordinateIndex)mesh.coords_.size() ) {
								coord.u = mesh.coords_[c].u();
								coord.v = mesh.coords_[c].v();
							}
							else {
								coord.u = 0;
								coord.v = 0;
							}
							coords.push_back( coord );
						}
					}
					v3 = v;
				}}

				// Create triangle
				trgls.push_back( Niflib::Triangle(v1,v2,v3) );
			}

			// Add vertices, normals, texture coordinates and triangles
			Niflib::NiTriStripsDataRef strdata = Niflib::DynamicCast<Niflib::NiTriStripsData>( Niflib::NiTriStripsData::Create() );
			strdata->SetVertices(vertices);
			if ( has_normals ) strdata->SetNormals(normals);
			if ( has_texture_coordinates ) {
				strdata->SetUVSetCount(1);
				strdata->SetUVSet(0,coords);
			}
			strdata->SetTriangles(trgls);

			// Set strips data
			strips->SetData( strdata );

			// Add to the root scene
			node->AddChild( Niflib::NiAVObjectRef(strips) );
		}
	}

	//==============================================================

	// Create BSX flag
	Niflib::BSXFlagsRef bsxflag = Niflib::DynamicCast<Niflib::BSXFlags>( Niflib::BSXFlags::Create() );
	bsxflag->SetData( 2 );
	bsxflag->SetName("BSX");
	node->AddExtraData( bsxflag );

	//==============================================================

	// Write file
	Niflib::NifInfo info;
	info.version = Niflib::VER_20_0_0_5;
	info.userVersion = 11;
	info.userVersion2 = 11;
	info.endian = Niflib::ENDIAN_LITTLE;
	Niflib::WriteNifTree(file, node, info);

	// Return success
	return true;
}

//! Load the mesh from a 3DS file (3D Studio Max File)
/*!
 *  This function uses the lib3ds library to load the 3DS file, provided under the GNU GPL 2.0 license.
 *
 @param mesh The mesh to load
 @param file The path to the 3DS file
 @param scale Rescale the vertex positions of the mesh
 @return True if mesh could be loaded, false otherwise
 */
bool TrglMeshIO::load_3ds_file(TrglMesh& mesh, const GEString& file, float scale) {
	// Try to open the 3DS file
	Lib3dsFile* f = lib3ds_file_open(file.c_str());
    if ( f == nil ) return false;

	// Create nodes if required
	if ( f->nodes == nil ) lib3ds_file_create_nodes_for_meshes(f);
	lib3ds_file_eval(f, 0);

	// Get directory path
	GEString dir_path;
	{{
		// Split the file name
		QStringList list = QString(file.c_str()).split("/");
		if ( list.isEmpty() == false ) {
			QStringList::const_iterator it = list.end(); --it; --it;
			for ( ; it != list.begin(); --it ) {
				dir_path = GEString(it->toAscii().constData()) + "/" + dir_path;
			}
			dir_path = GEString(list.begin()->toAscii().constData()) + "/" + dir_path;
		}
	}}

	// Get meshes
	load_3ds_nodes(mesh, f, f->nodes, dir_path, scale);

	// Release file
    lib3ds_file_free(f);
	return true;
}

//! Recursively load the mesh from a 3DS node
/*!
 *  This function uses the lib3ds library to load the 3DS file, provided under the GNU GPL 2.0 license.
 *
 @param mesh The mesh to load
 @param f The 3DS file
 @param node The first node of the 3DS file
 @param dir_path The path to the directory where the 3ds file resides
 @param scale Rescale the vertex positions of the mesh
 @return True if mesh could be loaded, false otherwise
 */
bool TrglMeshIO::load_3ds_nodes(TrglMesh& mesh, Lib3dsFile* f, Lib3dsNode* node, const GEString& dir_path, float scale) {
	bool res = false;
	if ( node == nil ) return res;

	// Clear the mesh
	mesh.vertices_.clear();
	mesh.normals_.clear();
	mesh.coords_.clear();
	mesh.trgl_groups_.clear();

	// Iterate over the nodes of the 3DS file
	for ( Lib3dsNode* p = node; p != nil; p = p->next) {
		// Get mesh nodes
		if ( p->type == LIB3DS_NODE_MESH_INSTANCE ) {
			// Access mesh
			Lib3dsMesh* m = lib3ds_file_mesh_for_node(f, p);
			if ( m == nil ) continue;
			if ( m->vertices == nil ) continue;

			// Get data cout
			size_t vcount = mesh.vertices_.size();
			size_t tcount = mesh.coords_.size();
			size_t ncount = mesh.normals_.size();

			// Get vertices
			Point3d<float> vertex;
			for ( long k = 0; k < m->nvertices; ++k ) {
				vertex.x() = ( m->vertices[k][0]*m->matrix[0][0] + m->vertices[k][1]*m->matrix[1][0] + m->vertices[k][2]*m->matrix[2][0] + m->matrix[3][0] ) * scale;
				vertex.y() = ( m->vertices[k][0]*m->matrix[0][1] + m->vertices[k][1]*m->matrix[1][1] + m->vertices[k][2]*m->matrix[2][1] + m->matrix[3][1] ) * scale;
				vertex.z() = ( m->vertices[k][0]*m->matrix[0][2] + m->vertices[k][1]*m->matrix[1][2] + m->vertices[k][2]*m->matrix[2][2] + m->matrix[3][2] ) * scale;
				mesh.vertices_.push_back( vertex );
			}

			// Get texture coordinates
			if ( m->texcos != nil ) {
				TextureCoordinate2d coord;
				for ( long k = 0; k < m->nvertices; ++k ) {
					coord.u() = m->texcos[k][0];
					coord.v() = m->texcos[k][1];
					mesh.coords_.push_back( coord );
				}
			}

			// Get faces
			TrglMeshGroup group;
			group.name() = p->name;
			TrglMeshSubGroup subgroup;
			int mat_index = -1;
			for ( long k = 0; k < m->nfaces; ++k ) {
				// Check material associated to the face
				if ( mat_index != m->faces[k].material ) {
					if ( subgroup.face_count() > 0 ) group.subgroups().push_back( subgroup );
					subgroup.clear();

					// Update material index
					mat_index = m->faces[k].material;
					if (mat_index != -1) {
						// Get material
						Lib3dsMaterial* mat = f->materials[mat_index];

						// Material name
						subgroup.material().name() = mat->name;

						// Ambient color
						{{
							ColorRGBA color;
							color.r() = mat->ambient[0];
							color.g() = mat->ambient[1];
							color.b() = mat->ambient[2];
							subgroup.material().set_ambient_color( color );
						}}

						// Diffuse color
						{{
							ColorRGB color;
							color.r() = mat->diffuse[0];
							color.g() = mat->diffuse[1];
							color.b() = mat->diffuse[2];
							subgroup.material().set_color( color );
						}}

						// Specular color
						{{
							ColorRGBA color;
							color.r() = mat->specular[0];
							color.g() = mat->specular[1];
							color.b() = mat->specular[2];
							subgroup.material().set_specular_color( color );
						}}

						// Shininess
						subgroup.material().set_shininess( 1000.0 * mat->shininess );

						// Transparency
						subgroup.material().set_alpha( 1.0f - mat->transparency );

						// Texture
						{{
							bool use_transparency = false;
							GEString filename = mat->texture1_map.name;
							Texture2d texture = Texture2dManager::instance()->get_texture(dir_path.c_str() + filename);
							if ( texture.initialized() == false ) {
								texture = Texture2d();
								GEString path = dir_path + filename;
								QImage img; img.load(path.c_str());
								texture.bind_texture(true);
								texture.set_parameters(Texture::UseMipmapMode);
								texture.set_parameters(Texture::ModulateMode);
								texture.set_parameters(Texture::RepeatMode);
								texture.set_parameters(Texture::LinearMagFilter);
								texture.set_parameters(Texture::LinearMipmapLinearMinFilter);
								texture.set_texture(img, Texture::RGBAInternalFormat, &use_transparency);
								texture.unbind_texture();
								Texture2dManager::instance()->add_texture(texture,dir_path.c_str() + filename);
							}

							// Add the texture to the material
							subgroup.material().set_texture(&texture);
							subgroup.use_transparency() = use_transparency;
						}}
					}
				}

				// Create triangle
				IndexedTrgl trgl;
				trgl.index1() = (SurfaceMesh::VertexIndex)(m->faces[k].index[0] + vcount);
				trgl.index2() = (SurfaceMesh::VertexIndex)(m->faces[k].index[1] + vcount);
				trgl.index3() = (SurfaceMesh::VertexIndex)(m->faces[k].index[2] + vcount);
				if ( m->texcos != nil ) {
					trgl.use_texture_coordinates(true);
					trgl.cindex1() = (SurfaceMesh::TextureCoordinateIndex)(m->faces[k].index[0] + tcount);
					trgl.cindex2() = (SurfaceMesh::TextureCoordinateIndex)(m->faces[k].index[1] + tcount);
					trgl.cindex3() = (SurfaceMesh::TextureCoordinateIndex)(m->faces[k].index[2] + tcount);
				}

				// Add triangle
				subgroup.triangles().push_back( trgl );
			}

			// Add triangle group
			if ( subgroup.face_count() > 0 ) group.subgroups().push_back( subgroup );
			if ( group.face_count() ) mesh.trgl_groups_.push_back( group );
			res = true;
		}

		// Add the children of this node
		res |= load_3ds_nodes(mesh, f, p->childs, dir_path, scale);
	}

	// Return result
	return res;
}

//! Save the mesh to a 3DS file (3D Studio Max File)
/*!
 *  This function uses the lib3ds library to save the 3DS file, provided under the GNU GPL 2.0 license.
 *
 @param mesh The mesh to save
 @param file The path to the 3DS file
 @return True if mesh could be loaded, false otherwise
 */
bool TrglMeshIO::save_3ds_file(const TrglMesh& mesh, const GEString& file) {
	// Create the 3ds file
	Lib3dsFile* f = lib3ds_file_new();
	if ( f == nil ) return false;

	// Get directory path
	GEString dir_path;
	{{
		// Split the file name
		QStringList list = QString(file.c_str()).split("/");
		if ( list.isEmpty() == false ) {
			QStringList::const_iterator it = list.end(); --it; --it;
			for ( ; it != list.begin(); --it ) {
				dir_path = GEString(it->toAscii().constData()) + "/" + dir_path;
			}
			dir_path = GEString(list.begin()->toAscii().constData()) + "/" + dir_path;
		}
	}}

	// Populate the file with meshes
	std::set<GEString> names, matnames;
	int mat_idx = -1;
	int idx = 0, idx2 = 0;
	std::map<int,int> mtls;
	for ( std::vector<TrglMeshGroup>::const_iterator it = mesh.trgl_groups_.begin(); it != mesh.trgl_groups_.end(); ++it ) {
		// Test if name already exists
		GEString gname = it->name();
		{{
			std::set<GEString>::iterator nit = names.find(gname);
			int nidx = 0;
			while ( nit != names.end() ) {
				QString num; num.setNum(nidx);
				gname = it->name();
				gname += "_";
				gname += num.toAscii().constData();
				++nidx;
				nit = names.find(gname);
			}
		}}
		names.insert(gname);

		// Create a new mesh
		Lib3dsMesh* m = lib3ds_mesh_new( gname.c_str() );
		if ( m == nil ) continue;

		//==============================================================

		// Create vertex/texture coordinates lists
		std::vector< Point3d<float> > vertices;
		std::vector< TextureCoordinate2d > coords;
		std::map<int,int> vmap;

		// Create triangles
		std::vector< Point3d<int> > trgls;

		// Find texture coordinates flags
		bool has_texture_coordinates = false;
		for ( std::vector<TrglMeshSubGroup>::const_iterator it2 = it->subgroups().begin(); it2 != it->subgroups().end(); ++it2 ) {
			for ( std::vector<IndexedTrgl>::const_iterator tit = it2->triangles().begin(); tit != it2->triangles().end(); ++tit ) {
				// Test texture coordinates flag
				if ( tit->has_texture_coordinates() == false ) continue;
				has_texture_coordinates = true;
				break;
			}
			if ( has_texture_coordinates ) break;
		}

		// Add subgroups
		for ( std::vector<TrglMeshSubGroup>::const_iterator it2 = it->subgroups().begin(); it2 != it->subgroups().end(); ++it2 ) {
			// Add material
			const Material& mat = it2->material();

			// Test if name already exists
			GEString mname = mat.name();
			{{
				std::set<GEString>::iterator nit = matnames.find(mname);
				int nidx = 0;
				while ( nit != matnames.end() ) {
					QString num; num.setNum(nidx);
					mname = it->name();
					mname += "_";
					mname += num.toAscii().constData();
					++nidx;
					nit = matnames.find(mname);
				}
			}}
			matnames.insert( mname );

			Lib3dsMaterial* material = lib3ds_material_new( mname.c_str() );
			lib3ds_file_insert_material(f, material, -1);

			// Ambient color
			if ( mat.active_parameter(Material::AmbientMaterial) ) {
				material->ambient[0] = mat.ambient_color().r();
				material->ambient[1] = mat.ambient_color().g();
				material->ambient[2] = mat.ambient_color().b();
			}

			// Diffuse color
			if ( mat.active_parameter(Material::ColorMaterial) ) {
				material->diffuse[0] = mat.color().r();
				material->diffuse[1] = mat.color().g();
				material->diffuse[2] = mat.color().b();
			}
			else if ( mat.active_parameter(Material::DiffuseMaterial) ) {
				material->diffuse[0] = mat.diffuse_color().r();
				material->diffuse[1] = mat.diffuse_color().g();
				material->diffuse[2] = mat.diffuse_color().b();
			}

			// Specular color
			if ( mat.active_parameter(Material::SpecularMaterial) ) {
				material->specular[0] = mat.specular_color().r();
				material->specular[1] = mat.specular_color().g();
				material->specular[2] = mat.specular_color().b();
			}

			// Shininess
			if ( mat.active_parameter(Material::ShininessMaterial) ) {
				material->shininess = mat.shininess() / 1000.0f;
			}

			// Transparency
			if ( mat.active_parameter(Material::AlphaMaterial) ) {
				material->transparency = 1.0f - mat.alpha();
			}

			// Texture
			if ( mat.active_parameter(Material::TexturedMaterial) ) {
				GEString texpath = "";
				if ( Texture2dManager::instance()->get_texture_name(texpath, *(mat.texture())) == true ) {
					QString str(texpath.c_str());
					str.replace(dir_path.c_str(),"");
					GEString sname = str.toAscii().constData();
					const char* name = sname.c_str();
					int sz = ( (int)strlen(name) < 63 ? (int)strlen(name) : 63 );
					for ( int k = 0; k < sz; ++k ) {
						material->texture1_map.name[k] = name[k];
					}
					material->texture1_map.name[sz] = '\0';
				}
			}
			++mat_idx;
			mtls.insert(std::pair<int,int>(idx,mat_idx));

			// Add triangles
			for ( std::vector<IndexedTrgl>::const_iterator tit = it2->triangles().begin(); tit != it2->triangles().end(); ++tit ) {
				// Get the current triangle
				const IndexedTrgl& trgl = *tit;
				++idx;

				// Compute indexes
				SurfaceMesh::VertexIndex v1 = 0, v2 = 0, v3 = 0;
				{{
					SurfaceMesh::VertexIndex v = trgl.index1();
					SurfaceMesh::TextureCoordinateIndex c = trgl.cindex1();
					std::map<int,int>::const_iterator mvit = vmap.find(v);
					if ( mvit != vmap.end() ) v = mvit->second;
					else {
						// Add vertex
						int idx = (int)vertices.size();
						Point3d<float> t;
						t.x() = mesh.vertices_[v].x();
						t.y() = mesh.vertices_[v].y();
						t.z() = mesh.vertices_[v].z();
						vertices.push_back( t );
						vmap.insert( std::pair<int,int>(v,idx) );
						v = idx;

						// Add texture coordinate
						if ( has_texture_coordinates ) {
							TextureCoordinate2d coord;
							if ( c < (SurfaceMesh::TextureCoordinateIndex)mesh.coords_.size() ) {
								coord.u() = mesh.coords_[c].u();
								coord.v() = mesh.coords_[c].v();
							}
							else {
								coord.u() = 0;
								coord.v() = 0;
							}
							coords.push_back( coord );
						}
					}
					v1 = v;

					v = trgl.index2();
					c = trgl.cindex2();
					mvit = vmap.find(v);
					if ( mvit != vmap.end() ) v = mvit->second;
					else {
						// Add vertex
						int idx = (int)vertices.size();
						Point3d<float> t;
						t.x() = mesh.vertices_[v].x();
						t.y() = mesh.vertices_[v].y();
						t.z() = mesh.vertices_[v].z();
						vertices.push_back( t );
						vmap.insert( std::pair<int,int>(v,idx) );
						v = idx;

						// Add texture coordinate
						if ( has_texture_coordinates ) {
							TextureCoordinate2d coord;
							if ( c < (SurfaceMesh::TextureCoordinateIndex)mesh.coords_.size() ) {
								coord.u() = mesh.coords_[c].u();
								coord.v() = mesh.coords_[c].v();
							}
							else {
								coord.u() = 0;
								coord.v() = 0;
							}
							coords.push_back( coord );
						}
					}
					v2 = v;

					v = trgl.index3();
					c = trgl.cindex3();
					mvit = vmap.find(v);
					if ( mvit != vmap.end() ) v = mvit->second;
					else {
						// Add vertex
						int idx = (int)vertices.size();
						Point3d<float> t;
						t.x() = mesh.vertices_[v].x();
						t.y() = mesh.vertices_[v].y();
						t.z() = mesh.vertices_[v].z();
						vertices.push_back( t );
						vmap.insert( std::pair<int,int>(v,idx) );
						v = idx;

						// Add texture coordinate
						if ( has_texture_coordinates ) {
							TextureCoordinate2d coord;
							if ( c < (SurfaceMesh::TextureCoordinateIndex)mesh.coords_.size() ) {
								coord.u() = mesh.coords_[c].u();
								coord.v() = mesh.coords_[c].v();
							}
							else {
								coord.u() = 0;
								coord.v() = 0;
							}
							coords.push_back( coord );
						}
					}
					v3 = v;
				}}

				// Create triangle
				trgls.push_back( Point3d<int>(v1,v2,v3) );
			}
		}

		// Add vertices
		lib3ds_mesh_resize_vertices(m, (int)vertices.size(), coords.empty() == false, 0);
		for ( int k = 0; k < m->nvertices; ++k ) {
			m->vertices[k][0] = vertices[k].x();
			m->vertices[k][1] = vertices[k].y();
			m->vertices[k][2] = vertices[k].z();

			if ( coords.empty() == false ) {
				m->texcos[k][0] = coords[k].u();
				m->texcos[k][1] = coords[k].v();
			}
		}

		// Add faces
		lib3ds_mesh_resize_faces(m, (int)trgls.size());
		int mtl_idx = -1;
		for ( int k = 0; k < m->nfaces; ++k ) {
			std::map<int,int>::iterator mtlsit = mtls.find(idx2);
			if ( mtlsit != mtls.end() ) {
				mtl_idx = mtlsit->second;
			}
			m->faces[k].index[0] = trgls[k].x();
			m->faces[k].index[1] = trgls[k].y();
			m->faces[k].index[2] = trgls[k].z();
			m->faces[k].material = mtl_idx;
			m->faces[k].flags = 7;
			m->faces[k].smoothing_group = 1;
			++idx2;
		}

		// Add the mesh to the file
		lib3ds_file_insert_mesh(f, m, -1);
	}

	// Save the file
	bool res = lib3ds_file_save(f, file.c_str());

	// Clear memory and return success status
	lib3ds_file_free(f);
	return res;
}

#ifdef GAMEENGINE_NAMESPACES
} // namespace GameEngine
#endif
